package com.wemall.core.tools;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class HttpInclude {
    private static final Logger log = LoggerFactory.getLogger(HttpInclude.class);

    public static String sessionIdKey = "JSESSIONID";
    private HttpServletRequest request;
    private HttpServletResponse response;
    private static final String SET_COOKIE_SEPARATOR = "; ";

    public HttpInclude(HttpServletRequest request, HttpServletResponse response){
        this.request = request;
        this.response = response;
    }

    public String include(String includePath){
        StringWriter sw = new StringWriter(8192);
        include(includePath, sw);

        return sw.toString();
    }

    public void include(String includePath, Writer writer){
        try {
            if (isRemoteHttpRequest(includePath))
                getRemoteContent(includePath, writer);
            else
                getLocalContent(includePath, writer);
        } catch (ServletException e){
            throw new RuntimeException("include error,path:" + includePath +
                                       " cause:" + e, e);
        } catch (IOException e){
            throw new RuntimeException("include error,path:" + includePath +
                                       " cause:" + e, e);
        }
    }

    private static boolean isRemoteHttpRequest(String includePath){
        return (includePath != null) && ((includePath.toLowerCase().startsWith("http://")) ||
                                         (includePath.toLowerCase().startsWith("https://")));
    }

    private void getLocalContent(String includePath, Writer writer) throws ServletException, IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);
        CustomOutputHttpServletResponseWrapper customResponse = new CustomOutputHttpServletResponseWrapper(this.response, writer, outputStream);
        String url_path = includePath.indexOf("?") > 0 ? includePath.substring(0, includePath.indexOf("?")) : includePath;
        String query = includePath.indexOf("?") > 0 ? includePath.substring(includePath.indexOf("?") + 1) : "";
        String[] params = query.split("&");
        for (String param : params){
            if ((param != null) && (!param.equals(""))){
                String[] list = param.split("=");
                if (list.length == 2)
                    this.request.setAttribute(list[0], list[1]);
            }
        }
        this.request.getRequestDispatcher(url_path).include(this.request, customResponse);
        customResponse.flushBuffer();
        if (customResponse.useOutputStream){
            writer.write(outputStream.toString(this.response.getCharacterEncoding()));
        }
        writer.flush();
    }

    private void getRemoteContent(String urlString, Writer writer)
    throws MalformedURLException, IOException {
        URL url = new URL(getWithSessionIdUrl(urlString));
        URLConnection conn = url.openConnection();
        setConnectionHeaders(urlString, conn);
        InputStream input = conn.getInputStream();
        try {
            Reader reader = new InputStreamReader(input, Utils.getContentEncoding(conn, this.response));
            Utils.copy(reader, writer);
        } finally {
            if (input != null)
                input.close();
        }
        writer.flush();
    }

    private void setConnectionHeaders(String urlString, URLConnection conn){
        conn.setReadTimeout(6000);
        conn.setConnectTimeout(6000);
        String cookie = getCookieString();
        conn.setRequestProperty("Cookie", cookie);

        if (log.isDebugEnabled())
            log.debug("request properties:" + conn.getRequestProperties() + " for url:" + urlString);
    }

    private String getWithSessionIdUrl(String url){
        return url;
    }

    private String getCookieString(){
        StringBuffer sb = new StringBuffer(64);
        Cookie[] cookies = this.request.getCookies();
        if (cookies != null){
            for (Cookie c : cookies){
                if (sessionIdKey.equals(c.getName())) continue;
                sb.append(c.getName()).append("=").append(c.getValue()).append("; ");
            }

        }

        String sessionId = Utils.getSessionId(this.request);
        if (sessionId != null){
            sb.append(sessionIdKey).append("=").append(sessionId).append(
                "; ");
        }

        return sb.toString();
    }

    public static class CustomOutputHttpServletResponseWrapper extends HttpServletResponseWrapper {
        public boolean useWriter = false;
        public boolean useOutputStream = false;
        private PrintWriter printWriter;
        private ServletOutputStream servletOutputStream;

        public CustomOutputHttpServletResponseWrapper(HttpServletResponse response, Writer customWriter, final OutputStream customOutputStream){
            super(response);
            this.printWriter = new PrintWriter(customWriter);
            this.servletOutputStream = new ServletOutputStream(){
                public void write(int b) throws IOException {
                    customOutputStream.write(b);
                }

                public void write(byte[] b) throws IOException {
                    customOutputStream.write(b);
                }

                public void write(byte[] b, int off, int len)
                throws IOException {
                    customOutputStream.write(b, off, len);
                }
            };
        }

        public PrintWriter getWriter() throws IOException {
            if (this.useOutputStream)
                throw new IllegalStateException(
                    "getOutputStream() has already been called for this response");
            this.useWriter = true;
            return this.printWriter;
        }

        public ServletOutputStream getOutputStream() throws IOException {
            if (this.useWriter)
                throw new IllegalStateException(
                    "getWriter() has already been called for this response");
            this.useOutputStream = true;
            return this.servletOutputStream;
        }

        public void flushBuffer() throws IOException {
            if (this.useWriter)
                this.printWriter.flush();
            if (this.useOutputStream)
                this.servletOutputStream.flush();
        }
    }

    static class Utils {
        static Pattern p = Pattern.compile("(charset=)(.*)",
                                           2);

        static String getContentEncoding(URLConnection conn, HttpServletResponse response){
            String contentEncoding = conn.getContentEncoding();
            if (conn.getContentEncoding() == null){
                contentEncoding = parseContentTypeForCharset(conn
                                  .getContentType());
                if (contentEncoding == null)
                    contentEncoding = response.getCharacterEncoding();
            }else{
                contentEncoding = conn.getContentEncoding();
            }
            return contentEncoding;
        }

        private static String parseContentTypeForCharset(String contentType){
            if (contentType == null)
                return null;
            Matcher m = p.matcher(contentType);
            if (m.find()){
                return m.group(2).trim();
            }
            return null;
        }

        private static void copy(Reader in, Writer out) throws IOException {
            char[] buff = new char[8192];
            while (in.read(buff) >= 0)
                out.write(buff);
        }

        private static String getSessionId(HttpServletRequest request){
            HttpSession session = request.getSession(false);
            if (session == null){
                return null;
            }
            return session.getId();
        }
    }
}
